/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2010,2011,2012,2013 TELEMATICS LAB, Politecnico di Bari
 *
 * This file is part of LTE-Sim
 *
 * LTE-Sim is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation;
 *
 * LTE-Sim is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LTE-Sim; if not, see <http://www.gnu.org/licenses/>.
 *
 * Author: Giuseppe Piro <g.piro@poliba.it>
 */



#include "RandomDirection.h"
#include "../componentManagers/NetworkManager.h"
#include "../device/Gateway.h"
#include "../device/ENodeB.h"
#include "../device/UserEquipment.h"
#include "../load-parameters.h"

const double tau = 0.5;
double Tavg =  100;//in sec

RandomDirection::RandomDirection()
{
    SetMobilityModel(Mobility::RANDOM_DIRECTION);
    SetSpeed (0);
    SetSpeedDirection (0.0);
    SetPositionLastUpdate (0.0);
    SetHandover (false);
    SetLastHandoverTime (0.0);
    SetAbsolutePosition (NULL);
}


RandomDirection::~RandomDirection()
{
    DeleteAbsolutePosition ();
}

void
RandomDirection::UpdatePosition (double time)
{
#ifdef MOBILITY_DEBUG
    std::cout << "\t START MOBILITY MODEL for "<< GetNodeID () << std::endl;
#endif

    if (GetSpeed () == 0)
    {
#ifdef MOBILITY_DEBUG
        std::cout << "\t\t speed = 0 --> position has not been updated!"<< std::endl;
#endif
        return;
    }

    double timeInterval = time - GetPositionLastUpdate ();

    UserEquipment *thisNode = NetworkManager::Init ()->GetUserEquipmentByID (GetNodeID ());
    Cell *thisCell = thisNode->GetCell ();


    NetworkNode *targetNode = thisNode->GetTargetNode ();

#ifdef MOBILITY_DEBUG
    std::cout << "MOBILITY_DEBUG: User ID: " << GetNodeID ()
              << "\n\t Cell ID " <<
              NetworkManager::Init()->GetCellIDFromPosition (GetAbsolutePosition()->GetCoordinateX(),
                      GetAbsolutePosition()->GetCoordinateY())
              << "\n\t Initial Position (X): " << GetAbsolutePosition()->GetCoordinateX()
              << "\n\t Initial Position (Y): " << GetAbsolutePosition()->GetCoordinateY()
              << "\n\t Speed: " << GetSpeed()
              << "\n\t Speed Direction: " << GetSpeedDirection()
              << "\n\t Time Last Update: " << GetPositionLastUpdate()
              << "\n\t Time Interval: " << timeInterval
              << std::endl;
#endif
#ifdef MOBILITY_DEBUG_TAB
    std::cout <<  GetNodeID () << ""
              << GetAbsolutePosition()->GetCoordinateX() << " "
              << GetAbsolutePosition()->GetCoordinateY() << " "
              << GetSpeed() << " "
              << GetSpeedDirection() << " "
              << GetPositionLastUpdate() << " "
              << timeInterval << " "
              << std::endl;
#endif

    double shift = timeInterval * (GetSpeed()*(1000.0/3600.0));

    double shift_y =
        shift * sin (GetSpeedDirection());
    double shift_x =
        shift * cos (GetSpeedDirection());

    CartesianCoordinates *newPosition =
        new CartesianCoordinates(GetAbsolutePosition()->GetCoordinateX()+shift_x,
                                 GetAbsolutePosition()->GetCoordinateY()+shift_y);


    CartesianCoordinates *ENodeBPosition = targetNode->GetMobilityModel ()->GetAbsolutePosition ();

    double azimut = newPosition->GetPolarAzimut (ENodeBPosition);
    double newDistanceFromTheENodeB = newPosition->GetDistance (ENodeBPosition);


    if (newDistanceFromTheENodeB <= (thisCell->GetMinDistance() * 1000))
    {
    }
    else if (newDistanceFromTheENodeB > (thisCell->GetRadius()*1000))
    {
        if (GetHandover()== false)
        {
            //the UE must remain into the same cell
            CartesianCoordinates *Correction =
                new CartesianCoordinates ((newDistanceFromTheENodeB - (thisCell->GetRadius()*1000)) * cos(azimut),
                                          (newDistanceFromTheENodeB - (thisCell->GetRadius()*1000)) * sin(azimut));

            newPosition->SetCoordinateX(newPosition->GetCoordinateX() - Correction->GetCoordinateX());
            newPosition->SetCoordinateY(newPosition->GetCoordinateY() - Correction->GetCoordinateY());

            double speedDirection = (double)(rand() %360) * ((2*3.14)/360);
            SetSpeedDirection(speedDirection);

            delete Correction;
        }
        else if (newPosition->GetDistance(0.0, 0.0) >= GetTopologyBorder ())
        {
            CartesianCoordinates *Correction =
                new CartesianCoordinates ((newDistanceFromTheENodeB - (thisCell->GetRadius()*1000)) * cos(azimut),
                                          (newDistanceFromTheENodeB - (thisCell->GetRadius()*1000)) * sin(azimut));

            newPosition->SetCoordinateX(newPosition->GetCoordinateX() - Correction->GetCoordinateX());
            newPosition->SetCoordinateY(newPosition->GetCoordinateY() - Correction->GetCoordinateY());

            double speedDirection = (double)(rand() %360) * ((2*3.14)/360);
            SetSpeedDirection(speedDirection);

            delete Correction;
        }
    }

    SetAbsolutePosition(newPosition);
    SetPositionLastUpdate (time);

#ifdef MOBILITY_DEBUG
    std::cout << "\n\t Final Position (X): " << GetAbsolutePosition()->GetCoordinateX()
              << "\n\t Final Position (Y): " << GetAbsolutePosition()->GetCoordinateY()
              << std::endl;
#endif
#ifdef MOBILITY_DEBUG_TAB
    std::cout << GetAbsolutePosition()->GetCoordinateX() << " "
              << GetAbsolutePosition()->GetCoordinateY()
              << std::endl;
#endif

    delete newPosition;
}

ConstantDirection::ConstantDirection (   double speed, double speedDirection)
{
    SetSpeed (speed);
    SetSpeedDirection (speedDirection);
    SetMobilityModel(Mobility::CONSTANT_DIRECTION );
    SetPositionLastUpdate (0.0);
    SetHandover (false);
    SetLastHandoverTime (0.0);
    SetAbsolutePosition (NULL);
}

ConstantDirection:: ~ConstantDirection ()
{
    DeleteAbsolutePosition ();
}

double GetSeVP (UserEquipment* ue, NetworkNode *targetNode,double time,double distance)
{

    double sevp = 0;
    double speedDir = (ue->GetMobilityModel()->GetSpeedDirection()) * PI/180.0;

    double sigma = 0;
    double v = ue->GetMobilityModel()->GetSpeed();
    double DP = 0 ;
    {
        Cell* thisCell = ue->GetCell ();
        double angleij = thisCell->GetCellCenterPosition()->GetPolarAzimut(targetNode->GetMobilityModel()->GetAbsolutePosition() );
        double k = log10 (v);
        double limit = 0;
        if ((k >=0.5) && (k<=2))
        {
            sigma = speedDir - angleij;
            limit = PI/(2*k);
            if ( (sigma >= -limit)  && (sigma <= limit))
            {
                DP = k*0.5*cos(k*sigma);
            }
        }
        //std::cout<< " v " << v << " k "  << k  << " sigma " << sigma <<  " DP " << DP  << " limit "  << limit << std::endl;
    }
    double SP = 0;
    {
        double Tm = Tavg - time;
        double dm = v*Tm;
        double ds = distance;
        double disratio = pow ((ds/dm),2.0);
        SP = exp(-disratio);

        //std::cout<< " Tm " << Tm  << " time " << time   << " ds  "  << ds  << " dm " << dm << std::endl;
    }
    sevp = ( (pow(DP,tau)) * (pow(SP,1-tau)) );
    std::cout<< std::fixed << "UE: " <<  ue->GetIDNetworkNode() << " Target Sector/Cell: " << targetNode->GetIDNetworkNode()
             << " Time: " <<time << " DP: "  << DP << " SP: " << SP  << " SeVP: "  << sevp << std::endl;
    return sevp;
}

void
ConstantDirection::UpdatePosition (double time)
{

#ifdef MOBILITY_DEBUG
    std::cout << "\t START MOBILITY MODEL for "<< GetNodeID () << std::endl;
#endif

    if (GetSpeed () == 0)
    {
#ifdef MOBILITY_DEBUG
        std::cout << "\t\t speed = 0 --> position has not been updated!"<< std::endl;
#endif
        return;
    }

    double timeInterval = time - GetPositionLastUpdate ();

    UserEquipment *thisNode = NetworkManager::Init ()->GetUserEquipmentByID (GetNodeID ());
    Cell *thisCell = thisNode->GetCell ();

    NetworkNode *targetNode = thisNode->GetTargetNode ();

#ifdef MOBILITY_DEBUG
    std::cout << "MOBILITY_DEBUG: User ID: " << GetNodeID ()
              << "\n\t Cell ID " <<
              NetworkManager::Init()->GetCellIDFromPosition (GetAbsolutePosition()->GetCoordinateX(),
                      GetAbsolutePosition()->GetCoordinateY())
              << "\n\t Initial Position (X): " << GetAbsolutePosition()->GetCoordinateX()
              << "\n\t Initial Position (Y): " << GetAbsolutePosition()->GetCoordinateY()
              << "\n\t Speed: " << GetSpeed()
              << "\n\t Speed Direction: " << GetSpeedDirection()
              << "\n\t Time Last Update: " << GetPositionLastUpdate()
              << "\n\t Time Interval: " << timeInterval
              << std::endl;
#endif
#ifdef MOBILITY_DEBUG_TAB
    std::cout <<  GetNodeID () << ""
              << GetAbsolutePosition()->GetCoordinateX() << " "
              << GetAbsolutePosition()->GetCoordinateY() << " "
              << GetSpeed() << " "
              << GetSpeedDirection() << " "
              << GetPositionLastUpdate() << " "
              << timeInterval << " "
              << std::endl;
#endif

    double shift = timeInterval * (GetSpeed()*(1000.0/3600.0));

    double shift_y =
        shift * sin (GetSpeedDirection());
    double shift_x =
        shift * cos (GetSpeedDirection());

    CartesianCoordinates *newPosition =
        new CartesianCoordinates(GetAbsolutePosition()->GetCoordinateX()+shift_x,
                                 GetAbsolutePosition()->GetCoordinateY()+shift_y);

    SetAbsolutePosition(newPosition);
    SetPositionLastUpdate (time);

#ifdef MOBILITY_DEBUG
    std::cout << "\n\t Final Position (X): " << GetAbsolutePosition()->GetCoordinateX()
              << "\n\t Final Position (Y): " << GetAbsolutePosition()->GetCoordinateY()
              << std::endl;
#endif
#ifdef MOBILITY_DEBUG_TAB
    std::cout << GetAbsolutePosition()->GetCoordinateX() << " "
              << GetAbsolutePosition()->GetCoordinateY()
              << std::endl;
#endif

    //calculate SeVP for this UE with all Cells/Sector
    std::vector<ENodeB*> *listOfNodes = NetworkManager::Init ()->GetENodeBContainer ();
    std::vector<ENodeB*>::iterator it;
    for (it = listOfNodes->begin (); it != listOfNodes->end (); it++)
    {
        if ((*it)->GetIDNetworkNode () != targetNode->GetIDNetworkNode () )
        {

            NetworkNode *probableNewTargetNode = (*it);

            double distance = probableNewTargetNode->GetMobilityModel ()->
                              GetAbsolutePosition ()->GetDistance (newPosition);

            double sevp = GetSeVP(thisNode, probableNewTargetNode,time,distance);
        }
    }

    delete newPosition;
}
